package com.gigi.job;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gigi.common.Constant;
import com.gigi.entity.UserEntity;
import com.gigi.service.generated.UserService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 缓存预热任务
 * <p>
 * 为解决首次访问系统的用户主页加载过慢的问题，使用 Spring Scheduler
 * 定时任务来实现缓存预热，并通过分布式锁保证多机部署时定时任务不会重复执行。
 *
 * @author gigi
 */
@Component
@Slf4j
public class PreCacheJob {

    @Autowired
    private UserService userService;

    @Autowired(required = false)
    private RedisTemplate redisTemplate;

    @Autowired(required = false)
    private RedissonClient redissonClient;

    /**
     * 定时缓存 未登录时推全量
     * 每天0点执行
     */
    @Scheduled(cron = "0 23 18 * * *")
    public void preCache() {

        if (Objects.isNull(redisTemplate) || Objects.isNull(redissonClient)) {
            log.warn("preCache 未配置 RedisTemplate 或 RedissonClient，跳过缓存预热");
            return;
        }

        // redisson 分布式锁
        // (1) 获取锁
        RLock lock = redissonClient.getLock(Constant.PRE_CACHE_LOCK);
        // (2) 如果加锁成功，执行写缓存操作
        try {
            if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                log.info("preCache 加锁成功" + Thread.currentThread().getName());

                // 一页10条，缓存5页
                for (int i = 1; i <= 5; i++) {
                    // 1、查库
                    Page<UserEntity> userEntityPage = userService.page(new Page<>(i, 10));
                    // 2、脱敏
                    if (Objects.nonNull(userEntityPage) && CollUtil.isNotEmpty(userEntityPage.getRecords())) {
                        userEntityPage.getRecords().forEach(e -> e.setUserPassword(null));
                    }
                    // 3、写缓存
                    String cacheKey = StrUtil.format(Constant.USER_RECOMMEND_FULL_CACHE_KEY, i, 10);
                    try {
                        redisTemplate.opsForValue().set(cacheKey, userEntityPage, 24, TimeUnit.HOURS);
                        log.info("preCache 写缓存成功" + Thread.currentThread().getName());
                    } catch (Exception e) {
                        log.error("preCache 写缓存失败", e);
                    }
                }
            } else {
                log.info("preCache 加锁失败" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            log.error("preCache 加锁失败", e);
        } finally {
            // (3) 只释放自己的锁
            if (lock.isHeldByCurrentThread()) {
                log.info("preCache 释放锁" + Thread.currentThread().getName());
                lock.unlock();
            }
        }
    }

}
